using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;

namespace SAWD.AI
{


    public class AIJSONBuilder : MonoBehaviour
    {

        public List<GameObject> gameObjects;
        private readonly Dictionary<int, AIObject> aiObjects = new Dictionary<int, AIObject>();

        void Start()
        {
            if (gameObjects == null || gameObjects.Count == 0)
            {
                gameObjects = FindAllGOInScene();
            }
            Cache();
        }

        public string GetJSONState()
        {
            List<string> json = new List<string>();
            foreach (int instanceId in aiObjects.Keys)
            {
                if (aiObjects[instanceId].go == null) continue;
                json.Add(aiObjects[instanceId].ToString());
            }

            return "[" + String.Join(",", json.ToArray()) + "]";
        }

        void Cache()
        {
            foreach (var go in gameObjects)
            {
                Component[] scripts = go.GetComponents<Component>();
                foreach (var script in scripts)
                {
                    ParseComponent(script, go);
                }
            }
        }

        private AIObject GetAICache(int instanceId, GameObject go)
        {
            if (aiObjects.ContainsKey(instanceId))
            {
                return aiObjects[instanceId];
            }
            var ai = new AIObject();
            ai.goInstanceId = instanceId;
            ai.go = go;
            ai.name = go.name;
            aiObjects[instanceId] = ai;
            Debug.Log(instanceId + " - " + go.name);
            return aiObjects[instanceId];
        }

        private void ParseComponent(Component script, GameObject go)
        {
            var type = script.GetType();

            foreach (var field in type.GetFields())
            {
                AI attr = (AI)field.GetCustomAttribute(typeof(AI), false);
                if (attr != null && field.IsPublic)
                {
                    var aiField = new AIField
                    {
                        field = field,
                        description = attr.Description,
                        name = field.Name,
                        script = script
                    };

                    var aiCache = GetAICache(go.GetInstanceID(), go);
                    aiCache.fields.Add(aiField);
                }

            }

            foreach (var method in type.GetMethods())
            {
                AI attr = (AI)method.GetCustomAttribute(typeof(AI), false);
                if (attr != null && method.IsPublic)
                {

                    var aiMethod = new AIMethod
                    {
                        script = script,
                        name = method.Name,
                        description = attr.Description,
                        type = type,
                        method = method
                    };

                    var parameters = method.GetParameters();
                    if (parameters.Length > 0)
                    {
                        foreach (var param in parameters)
                        {
                            var paramType = param.GetType();
                            AI paramDescription = (AI)paramType.GetCustomAttribute(typeof(AI), false);
                            var aiParam = new AIParam
                            {
                                name = param.Name,
                                description = paramDescription?.Description ?? "",
                                type = paramType,
                                parameter = param
                            };

                            aiMethod.parameters.Add(aiParam);
                        }
                    }

                    var aiCache = GetAICache(go.GetInstanceID(), go);
                    aiCache.methods.Add(method.Name, aiMethod);
                }
            }
        }

        private List<GameObject> FindAllGOInScene()
        {
            List<GameObject> allGOs = new List<GameObject>();
            GameObject[] rootGOs = gameObject.scene.GetRootGameObjects();

            for (int i = 0; i < rootGOs.Length; i++)
                GetAllChildren(rootGOs[i].transform, allGOs);

            return allGOs;
        }

        private void GetAllChildren(Transform current, List<GameObject> arrayToFill)
        {
            arrayToFill.Add(current.gameObject);

            for (int i = 0; i < current.childCount; i++)
                GetAllChildren(current.GetChild(i), arrayToFill);
        }

        public bool CallMethod(string method)
        {
            var called = Newtonsoft.Json.JsonConvert.DeserializeObject<AICall>(method);
            if (called != null)
            {
                //Debug.Log(called.mind.ToString());
                foreach (var met in called.methods_calls)
                {
                    var instanceId = met.callOn?.goInstanceId;
                    if (instanceId != null && instanceId != 0)
                    {
                        var curObj = aiObjects[(int)instanceId];
                        var curMethod = curObj.methods[met.method];
                        object[] pars = new object[met.arguments.Length];
                        int i = 0;
                        foreach (var param in met.arguments)
                        {
                            pars[i] = System.ComponentModel.TypeDescriptor.GetConverter(curMethod.parameters[i].parameter.ParameterType).ConvertFromString(param.value);
                            i++;
                        }
                        curMethod.method.Invoke(curMethod.script, pars);
                    }
                }
                return true;
            }
            return false;
        }

    }

    [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter | AttributeTargets.Field)]
    class AI : Attribute
    {
        public string Description { get; } = "";
        public AI() { }
        public AI(string description) => Description = description;
    }


    class AICall
    {
        //public string mind;
        public AICallMethod[] methods_calls;
    }

    class AICallMethod
    {
        public string method;
        public AICallObject callOn;
        public AICallParameter[] arguments;
    }

    class AICallObject
    {
        public string name;
        public int goInstanceId;
    }

    class AICallParameter
    {
        public string argumentName;
        public string value;
    }

    class AIObject
    {
        public int goInstanceId;
        public GameObject go;
        public List<AIField> fields = new();
        public Dictionary<string, AIMethod> methods = new();
        public string name;

        public override string ToString()
        {
            List<object> pars = new();
            foreach (var par in fields)
            {
                pars.Add(par.ToObject());
            }
            List<object> meths = new();
            foreach (var par in methods.Values)
            {
                meths.Add(par.ToObject());
            }
            return Newtonsoft.Json.JsonConvert.SerializeObject(new { goInstanceId, go?.name, fields = pars, methods = meths });
        }
    }

    class AIMethod
    {
        public string name;
        public string description;
        public Component script;
        public Type type;
        public MethodInfo method;
        public List<AIParam> parameters = new List<AIParam>();

        public List<object> ParamsAsList()
        {
            List<object> pars = new List<object>();
            foreach (var par in parameters)
            {
                pars.Add(par.ToObject());
            }
            return pars;
        }

        public object ToObject()
        {
            return new { name, description, parameters = ParamsAsList() };
        }

        public override string ToString()
        {

            return Newtonsoft.Json.JsonConvert.SerializeObject(ToObject());
        }
    }

    class AIField
    {
        public string name;
        public string description;
        public FieldInfo field;
        public Component script;

        public object ToObject()
        {
            return new { name, description, type = field.FieldType.ToString(), value = field.GetValue(script) };
        }

        public override string ToString()
        {
            return Newtonsoft.Json.JsonConvert.SerializeObject(ToObject());
        }
    }

    class AIParam
    {
        public string name;
        public string description;
        public Type type;
        public ParameterInfo parameter;

        public object ToObject()
        {
            return new { name, description, type = parameter.ParameterType.ToString() };
        }

        public override string ToString()
        {
            return Newtonsoft.Json.JsonConvert.SerializeObject(ToObject());
        }
    }


}